Appearance
Simpler Topology — Design Spec
Rewrite of Topology.ts as Topology_Simple.ts. Same public interface, completely different internals. Three passes instead of five interleaved phases.
Public interface (unchanged)
Input: list of objects with projected vertices, occluding faces, and helper functions (projection, winding, front-face detection).
Output: endpoints, edge segments, intersection segments, occluding segments. Same types as current code.
Pass 1: Visibility
Compute what's visible. No labeling, no identity matching.
1a: Intersection lines
For each pair of front-facing faces from different objects:
- Intersect face planes in 3D, clip to both quads → world-space segment
- Project to screen
- Clip against all occluding faces, skipping the two objects that own the intersecting faces (skip-self rule)
- Output: visible clips tagged with source (which two faces, which edge each endpoint sits on, world coordinates)
1b: Edge visibility
For each front-facing edge of each object:
- Clip against all occluding faces → visible intervals
- Merge nearly-touching intervals (gap < 0.02 in screen t)
- Filter fake slivers: if both boundaries of a visible interval come from adjacent non-silhouette faces of the same object, discard it
- Output: visible clips tagged with source (which object, which edge, world coordinates of both ends)
1c: Collect
Gather all visible clips into a flat list. Each clip is:
- Two screen-space endpoints
- Two world-space endpoints
- Source tag:
No endpoint identity assigned yet. Just geometry and provenance.
Pass 2: Arrangement
Find all crossings and split everything.
2a: Find crossings
For each pair of visible clips from different objects:
- Skip if bounding boxes don't overlap (Flatbush spatial index)
- Compute 2D line intersection
- If crossing point falls within both clips (t in -0.01 to 1.01): record it
Each crossing records:
- Screen position
- World position (interpolated from both clips)
- References to both clips and the t value on each
2b: Split
For each clip that has crossing points:
- Sort crossing points by t along the clip
- Split the clip into sub-segments at each crossing point
- Each sub-segment inherits the parent clip's source tag
After splitting, no two clips cross in their interiors. The result is a planar graph.
2c: Depth classification (for occluding segments)
For each crossing, determine which clip is in front (closer to camera) at the crossing point using world-space depth. Record front/behind relationship.
For each front clip that passes over a behind clip's face:
- Find the entry and exit points (consecutive crossings on the same front-clip + behind-face pair)
- Create an occluding segment connecting entry to exit
Pass 3: Label
Assign endpoint identities based on source tags and position.
Corner detection
If a clip endpoint is near a mesh vertex (within 1% of edge length in world space):
- Label as corner: keyed by object + vertex index
Intersection point detection
If the source tag is an intersection line and the endpoint is unoccluded:
- Label as face intersection (fi): keyed by both face IDs + start/end
Occlusion boundary detection
If a clip endpoint was created by the visibility clipper (not a corner, not an intersection):
- Label as occlusion clip (oc): keyed by edge + occluder face + enter/exit
- Determine the occluder's polygon edge from the clip data
Crossing detection
If a clip endpoint was created by Pass 2 splitting:
- Label as edge crossing (ex): keyed by both edge IDs (canonical order)
Shared identity rule
When two endpoints from different clips land at the same screen position (within tolerance):
- They should share the same key
- Priority: corner > fi > oc > ex
- This replaces the current code's three registries and all the matching logic
Build output
- Register all labeled endpoints in the endpoint map
- Build edge segments from the split edge clips
- Build intersection segments from the split intersection clips
- Occluding segments already built in 2c
What disappears
edge_pointsregistry — replaced by position-based matching in Pass 3clip_identityregistry — not needed; fi endpoints are identified by source tag, not by who needs to find them lateroc_at_occluder_edgeregistry — not needed; crossing endpoints are identified by the arrangement, not by pre-registrationused_fi_keys/prev_clip_end_keytracking — not needed; identities assigned after all geometry is done, so no risk of double-use- Phase 3 phantom filtering — not needed; endpoints aren't created until Pass 3, and only for clips that survived visibility
Key invariants to preserve
- Skip-self for intersection lines. Intersection lines between faces A and B must not be clipped by A or B's own faces.
- Fake sliver filtering. When an edge is hidden behind two adjacent non-silhouette faces of the same object, the seam must not produce a phantom visible interval.
- Merge micro-gaps. Visible intervals separated by less than 0.02 in screen t are artifacts and should be merged.
- Depth check for crossings. Only create occluding segments when the crossing edge is in front of the face, not behind it.
Geometry functions to reuse
These are pure math with no identity logic. Copy them directly:
intersect_face_pair— finds where two face planes meet, clips to both quadsclip_segment_for_occlusion_rich— clips a segment against all occluding faces, returns intervals with cause infoclip_segment_to_polygon_2d— Cyrus-Beck clip to convex polygonclip_to_quad_with_edges— clip parametric line to convex quad in 3Dintersect_2d— 2D line-line intersectionscreen_t— parametric t along a screen segment
Testing
The existing test suite (42 tests) runs against the public interface. Switch the import from Topology to Topology_Simple and all tests should pass:
- Layer 1: pure geometry (unaffected)
- Layer 2: clipping, fake slivers, skip-self (validates Pass 1)
- Layer 3: splitting (validates Pass 2)
- Layer 4: edge piercing, crossings, structural integrity (validates full pipeline)
- Layer 5: golden test (validates overall consistency)
Build order
- Stub class with empty
compute()returning empty output - Implement Pass 1a (intersection lines) — test: Layer 2 overlap tests pass
- Implement Pass 1b (edge visibility) — test: Layer 2 single-object and clipping tests pass
- Implement Pass 2 (arrangement) — test: Layer 3 and Layer 4 piercing tests pass
- Implement Pass 3 (labeling) — test: Layer 4 structural tests pass, Layer 5 golden test passes
- Wire into Render.ts behind a flag — compare output with old Topology side by side